// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef EIGEN_CAMERA_H
#define EIGEN_CAMERA_H

#include <Eigen/Geometry>
#include <QObject>
// #include <frame.h>

class Frame
{
  public:
	EIGEN_MAKE_ALIGNED_OPERATOR_NEW

	inline Frame(const Eigen::Vector3f& pos = Eigen::Vector3f::Zero(),
				 const Eigen::Quaternionf& o = Eigen::Quaternionf())
		: orientation(o)
		, position(pos)
	{
	}
	Frame lerp(float alpha, const Frame& other) const
	{
		return Frame((1.f - alpha) * position + alpha * other.position, orientation.slerp(alpha, other.orientation));
	}

	Eigen::Quaternionf orientation;
	Eigen::Vector3f position;
};

class Camera
{
  public:
	EIGEN_MAKE_ALIGNED_OPERATOR_NEW

	Camera(void);

	Camera(const Camera& other);

	virtual ~Camera();

	Camera& operator=(const Camera& other);

	void setViewport(uint offsetx, uint offsety, uint width, uint height);
	void setViewport(uint width, uint height);

	inline uint vpX(void) const { return mVpX; }
	inline uint vpY(void) const { return mVpY; }
	inline uint vpWidth(void) const { return mVpWidth; }
	inline uint vpHeight(void) const { return mVpHeight; }

	inline float fovY(void) const { return mFovY; }
	void setFovY(float value);

	void setPosition(const Eigen::Vector3f& pos);
	inline const Eigen::Vector3f& position(void) const { return mFrame.position; }

	void setOrientation(const Eigen::Quaternionf& q);
	inline const Eigen::Quaternionf& orientation(void) const { return mFrame.orientation; }

	void setFrame(const Frame& f);
	const Frame& frame(void) const { return mFrame; }

	void setDirection(const Eigen::Vector3f& newDirection);
	Eigen::Vector3f direction(void) const;
	void setUp(const Eigen::Vector3f& vectorUp);
	Eigen::Vector3f up(void) const;
	Eigen::Vector3f right(void) const;

	void setTarget(const Eigen::Vector3f& target);
	inline const Eigen::Vector3f& target(void) { return mTarget; }

	const Eigen::Affine3f& viewMatrix(void) const;
	const Eigen::Matrix4f& projectionMatrix(void) const;

	void rotateAroundTarget(const Eigen::Quaternionf& q);
	void localRotate(const Eigen::Quaternionf& q);
	void zoom(float d);

	void localTranslate(const Eigen::Vector3f& t);

	/** Setup OpenGL matrices and viewport */
	void activateGL(void);

	Eigen::Vector3f unProject(const Eigen::Vector2f& uv, float depth, const Eigen::Matrix4f& invModelview) const;
	Eigen::Vector3f unProject(const Eigen::Vector2f& uv, float depth) const;

  protected:
	void updateViewMatrix(void) const;
	void updateProjectionMatrix(void) const;

  protected:
	uint mVpX, mVpY;
	uint mVpWidth, mVpHeight;

	Frame mFrame;

	mutable Eigen::Affine3f mViewMatrix;
	mutable Eigen::Matrix4f mProjectionMatrix;

	mutable bool mViewIsUptodate;
	mutable bool mProjIsUptodate;

	// used by rotateAroundTarget
	Eigen::Vector3f mTarget;

	float mFovY;
	float mNearDist;
	float mFarDist;
};

#endif // EIGEN_CAMERA_H
